﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Dotway.WPF.Controls.Utilities;

namespace Dotway.WPF.Controls
{
    [TemplatePart(Name = "PART_ImageBorder", Type = typeof(Border))]
    public class ColorMap : Control
    {
        #region Fields

        private enum InteractionState
        {
            Idle = 0,
            MouseMove = 1
        };

        private enum ChangeSource
        {
            None = 0,
            SelectedColor = 1,
            Color = 2,
            Position = 3,
            HColor = 4
        };

        private InteractionState interactionState = InteractionState.Idle;
        private ChangeSource changeSource = ChangeSource.None;
        private Border imageBorder = null;
        private ColorMapData colorMapHelper = new ColorMapData();

        #endregion Fields
        #region Constructors

        public ColorMap()
        {
            this.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
        }

        static ColorMap()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorMap), new FrameworkPropertyMetadata(typeof(ColorMap)));
        }

        #endregion Constructors
        #region Properties
        #region HColor

        public static readonly DependencyProperty HColorProperty = DependencyProperty.Register("HColor", typeof(Color), typeof(ColorMap), new UIPropertyMetadata(Color.FromRgb(255, 0, 0), new PropertyChangedCallback(OnHColorChanged)));
        public Color HColor
        {
            get { return (Color)GetValue(HColorProperty); }
            set { SetValue(HColorProperty, value); }
        }

        private static void OnHColorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ColorMap control = o as ColorMap;
            if (control != null)
            {
                control.OnHColorChanged((Color)e.OldValue, (Color)e.NewValue);
            }
        }

        protected virtual void OnHColorChanged(Color oldValue, Color newValue)
        {
            if (changeSource != ChangeSource.None)
            {
                return;
            }
            changeSource = ChangeSource.HColor;

            UpdateColorMap();
            UpdateColorFromPosition();

            changeSource = ChangeSource.None;
        }

        #endregion HColor
        #region Position

        public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(Point), typeof(ColorMap), new UIPropertyMetadata(new Point(0.0, 0.0), new PropertyChangedCallback(OnPositionChanged)));
        public Point Position
        {
            get { return (Point)GetValue(PositionProperty); }
            set { SetValue(PositionProperty, value); }
        }

        private static void OnPositionChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ColorMap control = o as ColorMap;
            if (control != null)
            {
                control.OnPositionChanged((Point)e.OldValue, (Point)e.NewValue);
            }
        }

        protected virtual void OnPositionChanged(Point oldValue, Point newValue)
        {
            if (changeSource != ChangeSource.None)
            {
                return;
            }
            changeSource = ChangeSource.Position;

            newValue = LimitPosition(newValue);
            UpdateColorFromPosition(newValue);

            changeSource = ChangeSource.None;
        }

        #endregion Position        
        #region SelectedColor

        public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(NotifiedColor), typeof(ColorMap), new UIPropertyMetadata(new NotifiedColor(255, 255, 255), new PropertyChangedCallback(OnSelectedColorChanged)));
        public NotifiedColor SelectedColor
        {
            get { return (NotifiedColor)GetValue(SelectedColorProperty); }
            set { SetValue(SelectedColorProperty, value); }
        }

        private static void OnSelectedColorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ColorMap control = o as ColorMap;
            if (control != null)
            {
                control.OnSelectedColorChanged((NotifiedColor)e.OldValue, (NotifiedColor)e.NewValue);
            }
        }

        protected virtual void OnSelectedColorChanged(NotifiedColor oldValue, NotifiedColor newValue)
        {
            if (changeSource != ChangeSource.None)
            {
                return;
            }
            changeSource = ChangeSource.SelectedColor;

            if (oldValue != null)
            {
                // Release the old listener.
                oldValue.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(OnColorPropertyChanged);
            }

            if (newValue != null)
            {
                // Listen to color changed.
                newValue.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(OnColorPropertyChanged);
            }

            if (imageBorder != null)
            {
                Color hColor = HColor;
                Position = colorMapHelper.GetPosition(out hColor, (int)imageBorder.ActualWidth, (int)imageBorder.ActualHeight, newValue.Color);
                HColor = hColor;
                UpdateColorMap();
            }            

            changeSource = ChangeSource.None;
        }

        private void OnColorPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Color")
            {
                if (changeSource != ChangeSource.None)
                {
                    return;
                }
                changeSource = ChangeSource.Color;

                if (interactionState != InteractionState.MouseMove && imageBorder != null)
                {
                    Color hColor = HColor;
                    Position = colorMapHelper.GetPosition(out hColor, (int)imageBorder.ActualWidth, (int)imageBorder.ActualHeight, SelectedColor.Color);
                    HColor = hColor;
                    UpdateColorMap();
                }                

                changeSource = ChangeSource.None;
            }
        }

        #endregion SelectedColor
        #endregion Properties


        public override void OnApplyTemplate()
        {
            imageBorder = this.GetTemplateChild("PART_ImageBorder") as Border;
            if (imageBorder == null)
            {
                throw new NullReferenceException("Could not find templated part: PART_ImageBorder");
            }
            imageBorder.MouseLeftButtonUp += new MouseButtonEventHandler(OnMouseLeftButtonUp);
            imageBorder.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
            imageBorder.MouseMove += new MouseEventHandler(OnMouseMove);

            SelectedColor = SelectedColor;

            base.OnApplyTemplate();
        }

        private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            interactionState = InteractionState.MouseMove;
            imageBorder.CaptureMouse();
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Point currentPosition = e.GetPosition(imageBorder);
                SilentPositionUpdate(currentPosition);
                UpdateColorFromPosition();
            }
        }

        private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            Point currentPosition = e.GetPosition(imageBorder);
            SilentPositionUpdate(currentPosition);
            UpdateColorFromPosition();

            Mouse.Capture(null);
            interactionState = InteractionState.Idle;
        }

        private void UpdateColorFromPosition()
        {
            UpdateColorFromPosition(Position);
        }

        private void UpdateColorFromPosition(Point position)
        {
            Color newColor = colorMapHelper.GetColor(HColor, position, (int)imageBorder.ActualWidth, (int)imageBorder.ActualHeight);
            newColor.A = SelectedColor.A;
            SelectedColor.Color = newColor;            
        }

        private void SilentPositionUpdate(Point newPosition)
        {
            newPosition = LimitPosition(newPosition);

            changeSource = ChangeSource.Position;
            Position = newPosition;
            changeSource = ChangeSource.None;
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            // Trigger the update system, update position and colors.
            SelectedColor.Color = SelectedColor.Color;
        }

        private void UpdateColorMap()
        {
            if (imageBorder != null)
            {
                imageBorder.Child = colorMapHelper.GetColorMap(HColor, (int)imageBorder.ActualWidth, (int)imageBorder.ActualHeight);
            }
        }

        private Point LimitPosition(Point position)
        {
            Point newPosition = position;
            if (imageBorder != null)
            {
                position.X = Math.Min(position.X, imageBorder.ActualWidth);
                position.X = Math.Max(position.X, 0);

                position.Y = Math.Min(position.Y, imageBorder.ActualHeight);
                position.Y = Math.Max(position.Y, 0);

                newPosition = position;
            }
            else
            {
                newPosition.X = 0;
                newPosition.Y = 0;
            }            

            return newPosition;
        }
    }
}
